import network
import random
import datetime


class Evolution:

    def __init__(self, n):
        self.individuals = []
        self.n = n
        for i in range(n):
            #Each individual is initialized with a tuple of a 128-bit random phenotype and a fitness value of 0
            self.individuals.append([random.randint(0,0xffffffffffffffffffffffffffffffff), 0])
    
    def fitness(self, n_iter): #Update the fitness of each individual with the evaluation function from network.py
        
        average = 0.0
        
        CA = network.SmallWorld(99, 6, 0.0) #Create network as a CA (if p > 0, this should be inside the loop!)
        for creature in self.individuals:
            CA.update_rule_mitchell(creature[0]) #CA rule is the individual's phenotype (creature[0])
            creature[1] = (CA.evaluate_majority(n_iter)) #Update individual's fitness with evaluation of majority problem (creature[1])
            average += creature[1]
        
        return average/float(self.n)
        
        
    def selection(self, t_size): #Tournament selection with replacement
        sample = random.sample(self.individuals, t_size) #Sample t_size individuals for tournament
        sample.sort(key = lambda x: x[1]) #Use fitness as criteria for sorting
        return sample[t_size - 1][0] #Return phenotype of the best individual
        
    def sex(self, father, mother, mutation_rate): #Cross-over and mutation of two parents phenotype returning one offspring phenotype
        father_side = random.randint(0,0xffffffffffffffffffffffffffffffff)
        mother_side = ~father_side
        child = (father & father_side) | (mother & mother_side) #Random uniform crossover
        
        for i in range(128): #There is a mutation_rate of being flipped for each bit of the phenotype
            if random.random() < mutation_rate:
                child = child ^ (1 << i) 
        return child
        
    def reproduction(self): #New generation is created using tournament selection to select two parents who have one offspring 
        new_generation = []
        for i in range(self.n):
            father = self.selection(10)
            mother = self.selection(10)
            child = self.sex(father, mother, 0.02)
            new_generation.append([child, 0])
            
        self.individuals = new_generation #Exchange old individuals with the new generation

random.seed()

pop_size = 200
#Execution (arbitrary values - very long run-time as it is)
a = Evolution(pop_size)

f = open("evolution.txt", 'a')
t0 = datetime.datetime.now()
f.write("----------------------- "+str(t0)+" ----------------------------\n")
f.write("Pop "+str(pop_size)+" sim_time 100 fitness rep 200\n")

t0 = datetime.datetime.now()
for i in range(50):
    print("EPOCH "+str(i))
    avg = a.fitness(200)
    b = a.individuals
    b.sort(key = lambda x: x[1])
    print "Avg", avg, "Best:",b[pop_size-1][1], "Median:", b[pop_size/2][1], "Worst:", b[0][1]
    
    f.write("Epoch "+str(i)+"\n")
    f.write("Avg " + str(avg) + " Best: " + str(b[pop_size-1][1]) + " Median: " + str(b[pop_size/2][1]) + " Worst: " + str(b[0][1]) + "\n")
    f.write("Phenotype: " + str(b[pop_size-1][0]) + "\n")
    
    a.reproduction()
    print(datetime.datetime.now() - t0)

